#!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -W0

require "#{File.dirname __FILE__}/../libsuperenv"
require 'set'
require 'stringio'

class Logger
  def initialize
    @io = StringIO.new
  end

  def puts(*args)
    @io.puts(*args)
  end

  def log!
    return unless ENV.key? 'HOMEBREW_CC_LOG_PATH'
    path = "#{ENV['HOMEBREW_CC_LOG_PATH']}.cc"

    puts
    File.open(path, File::WRONLY | File::APPEND | File::CREAT) { |f| f.write(@io.string) }
  end
end

LOGGER = Logger.new

class Cmd
  attr_reader :brewfix, :brewtmp, :sdkroot

  def initialize path, args
    @arg0 = File.basename(path).freeze
    @args = args.freeze
    @brewfix = ENV['HOMEBREW_PREFIX']
    @brewtmp = ENV['HOMEBREW_TEMP']
    @sdkroot = ENV['HOMEBREW_SDKROOT']
  end
  def mode
    if @arg0 == 'cpp' or @arg0 == 'ld'
      @arg0.to_sym
    elsif @args.include? '-c'
      if @arg0 =~ /(?:c|g|clang)\+\+/
        :cxx
      else
        :cc
      end
    elsif @args.include? '-E'
      :ccE
    else
      if @arg0 =~ /(?:c|g|clang)\+\+/
        :cxxld
      else
        :ccld
      end
    end
  end
  def tool
    @tool ||= case @arg0
    when 'ld' then 'ld'
    when 'cpp' then 'cpp'
    when /\w\+\+(-\d\.\d)?$/
      case ENV['HOMEBREW_CC']
      when /clang/
        'clang++'
      when /llvm-gcc/
        'llvm-g++-4.2'
      when /gcc(-\d\.\d)?$/
        'g++' + $1.to_s
      end
    else
      # Note that this is a universal fallback, so that we'll always invoke
      # HOMEBREW_CC regardless of what name under which the tool was invoked.
      ENV['HOMEBREW_CC']
    end
  end
  def args
    args = if not cccfg? 'O' or tool == 'ld'
      @args.dup
    else
      refurbished_args
    end
    if tool != 'ld'
      args << "--sysroot=#{sdkroot}"
    else
      args << "-syslibroot" << sdkroot
    end if nclt?
    allflags = case mode
    when :ccld
      cflags + args + cppflags + ldflags
    when :cxxld
      cxxflags + args + cppflags + ldflags
    when :cc
      cflags + args + cppflags
    when :cxx
      cxxflags + args + cppflags
    when :ccE
      args + cppflags
    when :cpp
      args + cppflags
    when :ld
      ldflags + args
    end.compact
    make_fuss(allflags)
    allflags
  end
  def refurbished_args
    lset = Set.new(libpath + syslibpath)
    iset = Set.new(cpath.flatten)

    args = []
    whittler = @args.each
    loop do
      case arg = whittler.next
      when '-arch', /^-Xarch_/
        whittler.next
      when '-m32'
        # If ENV.m32 was set, we allow the "-m32" flag, but we don't add anything
        args << '-m32' if cccfg? '3'
      when /^-g\d?/, /^-gstabs\d+/, '-gstabs+', /^-ggdb\d?/, '-gdwarf-2',
           /^-march=.+/, /^-mtune=.+/, /^-mcpu=.+/, '-m64',
           /^-O[0-9zs]?$/, '-fast', '-no-cpp-precomp',
           '-pedantic', '-pedantic-errors'
      when '-fopenmp', '-lgomp'
        # clang doesn't support OpenMP
        args << arg if not tool =~ /^clang/
      when /^-W.*/
        args << arg if arg =~ /^-W[alp],/ or arg =~ /^-Wno-/
      when '-macosx_version_min', '-dylib_install_name'
        args << "-Wl,#{arg},#{whittler.next}"
      when /^-isysroot/
        # We set the sysroot
        whittler.next
      when '-dylib'
        args << "-Wl,#{arg}"
      when /^-I(.+)?/
        # Support both "-Ifoo" (one argument) and "-I foo" (two arguments)
        path = $1.chuzzle || whittler.next
        args << "-I#{path}" if keep?(path) and iset.add?(path.cleanpath)
      when /^-L(.+)?/
        path = $1.chuzzle || whittler.next
        args << "-L#{path}" if keep?(path) and lset.add?(path.cleanpath)
      else
        args << arg
      end
    end
    args
  end

  def keep? path
    case path.cleanpath
    when %r{^#{Regexp.escape(brewfix)}}, %r{^#{Regexp.escape(brewtmp)}}
      # maybe homebrew is installed to /sw or /opt/brew
      true
    when %r{^/opt}, %r{^/sw}, %r{/usr/X11}
      false
    else
      true
    end
  end

  def cflags
    args = []

    return args unless cccfg? 'O' or configure?

    args << '-pipe'
    args << '-w' unless configure?
    args.concat(optflags)
    args.concat(archflags)
    args << "-std=#{@arg0}" if @arg0 =~ /c[89]9/
    args
  end
  def cxxflags
    args = cflags
    args << '-std=c++11' if cccfg? 'x'
    args << '-stdlib=libc++' if cccfg? 'g'
    args << '-stdlib=libstdc++' if cccfg? 'h'
    args
  end
  def optflags
    args = []
    args << "-#{ENV['HOMEBREW_OPTIMIZATION_LEVEL']}"
    args.concat ENV['HOMEBREW_OPTFLAGS'].split(' ') if ENV['HOMEBREW_OPTFLAGS']
    args
  end
  def archflags
    args = []
    args.concat ENV['HOMEBREW_ARCHFLAGS'].split(' ') if cccfg? 'u'
    args
  end
  def syspath
    if nclt?
      %W{#{sdkroot}/usr #{sdkroot}/usr/local}
    else
      %W{/usr /usr/local}
    end
  end
  def syslibpath
    # We reject brew's lib as we explicitly add this as a -L flag, thus it
    # is given higher priority by cc, so it surpasses the system libpath.
    # NOTE this only counts if Homebrew is installed at /usr/local
    syspath.map{|d| "#{d}/lib" }.reject{|d| d == "#{brewfix}/lib" }
  end
  def syscpath
    isystem, _ = cpath
    isystem + syspath.map{|d| "#{d}/include" }
  end
  def cpath
    cpath = ENV['CMAKE_PREFIX_PATH'].split(':').map{|d| "#{d}/include" } + ENV['CMAKE_INCLUDE_PATH'].split(':')
    opt = cpath.grep(%r{^#{Regexp.escape(brewfix)}/opt})
    sys = cpath - opt
    [sys, opt]
  end
  def libpath
    ENV['CMAKE_PREFIX_PATH'].split(':').map{|d| "#{d}/lib" } +
    ENV['CMAKE_LIBRARY_PATH'].split(':') -
    syslibpath
  end
  def ldflags
    args = libpath.to_flags('-L')
    case mode
    when :ld   then args << '-headerpad_max_install_names'
    when :ccld then args << '-Wl,-headerpad_max_install_names'
    when :cxxld
      args << '-Wl,-headerpad_max_install_names'
      args << '-stdlib=libc++' if cccfg? 'g'
      args << '-stdlib=libstdc++' if cccfg? 'h'
    end
    args
  end
  def cppflags
    sys, opt = cpath
    # we want our keg-only includes to be found before system includes *and*
    # before any other includes the build-system adds
    sys.to_flags('-isystem') + opt.to_flags('-I')
  end
  def make_fuss args
    return unless make_fuss?

    dels = @args - args
    adds = args - @args
    dups = dels & args

    LOGGER.puts "superenv removed:  #{dels*' '}" unless dels.empty?
    LOGGER.puts "superenv deduped:  #{dups}" unless dups.empty?
    LOGGER.puts "superenv added:    #{adds*' '}" unless adds.empty?
  end
  def make_fuss?
    cccfg? 'O' and not configure?
  end
  def configure?
    # configure scripts generated with autoconf 2.61 or later export as_nl
    ENV.key? 'as_nl'
  end
  def nclt?
    sdkroot != nil
  end
  def cccfg? flags
    flags.split('').all?{|c| ENV['HOMEBREW_CCCFG'].include? c } if ENV['HOMEBREW_CCCFG']
  end
end

if __FILE__ == $PROGRAM_NAME
  ##################################################################### sanity
  abort "The build-tool has reset ENV. --env=std required." unless ENV['HOMEBREW_BREW_FILE']

  case ENV['HOMEBREW_CC'].chuzzle when 'cc', nil
    # those values are not allowed
    ENV['HOMEBREW_CC'] = 'clang'
  end

  ####################################################################### main

  LOGGER.puts "#{File.basename($0)} called with: #{ARGV.join(" ")}"

  cmd = Cmd.new($0, ARGV)
  tool, args = cmd.tool, cmd.args

  LOGGER.puts "superenv executed: #{tool} #{args.join(" ")}"
  LOGGER.log!

  exec "xcrun", tool, *args
end
